// $Id: server_codec.c 70 2011-04-23 17:29:45Z cedric.shih@gmail.com $
/*
 * Copyright (C) 2011 Cedric Shih <cedric.shih@gmail.com>
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation; either version 2.1 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
 */

#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>

#include <nio.h>
#include <nio/buffer.h>
#include <nio/handler.h>
#include <niohttp/server.h>
#include <niohttp.h>

#include "log.h"

#include "server_codec.h"

struct niohandler niohttpd_server_handler = {
		niohttpd_server_codec_setup,
		niohttpd_server_codec_teardown,
		niohttpd_server_codec_handle_upstream,
		niohttpd_server_codec_handle_downstream,
		niohttpd_server_codec_free,
};

struct niohttpd_server_codec *niohttpd_server_codec_new(void)
{
	struct niohttpd_server_codec *codec;

	niohttpd_debug("constructing");

	if (!(codec = calloc(1, sizeof(*codec)))) {
		niohttpd_error("failed to calloc");
		goto exception;
	}

	if (!(codec->decoder = niohttp_request_decoder_new())) {
		niohttpd_error("failed to create request decoder");
		goto exception;
	}

	if (!(codec->encoder = niohttp_response_encoder_new())) {
		niohttpd_error("failed to create response encoder");
		goto exception;
	}

	return codec;

exception:
	niohttpd_server_codec_free(codec);
	return NULL;
}

void niohttpd_server_codec_free(void *obj)
{
	struct niohttpd_server_codec *codec = obj;

	niohttpd_debug("destructing: %p", codec);

	if (!(codec)) {
		return;
	}

	if (codec->encoder) {
		niohttp_response_encoder_free(codec->encoder);
	}

	if (codec->decoder) {
		niohttp_request_decoder_free(codec->decoder);
	}

	free(codec);
}

int niohttpd_server_codec_setup(void *obj, const char *name)
{
	int rc;
	struct niohttpd_server_codec *codec = obj;

	if (!codec || !name) {
		niohttpd_error("null argument(s)");
		rc = NIO_EINVAL;
		goto finally;
	}

	niohttpd_debug("setting up: %s", name);

	if ((rc = niohttp_request_decoder_setup(codec->decoder))) {
		niohttpd_error("failed to setup decoder: %s",
				niohttp_strerror(rc));
		goto finally;
	}

	if ((rc = niohttp_response_encoder_setup(codec->encoder))) {
		niohttpd_error("failed to setup encoder: %s",
				niohttp_strerror(rc));
		goto finally;
	}

	codec->name = name;
	rc = 0;

finally:
	return rc;
}

int niohttpd_server_codec_teardown(void *obj)
{
	int rc;
	struct niohttpd_server_codec *codec = obj;

	if (!codec) {
		niohttpd_error("null argument(s)");
		rc = NIOHTTPD_EINVAL;
		goto finally;
	}

	niohttpd_debug("tearing down: %s", codec->name);

	niohttp_request_decoder_teardown(codec->decoder);

	niohttp_response_encoder_teardown(codec->encoder);

	if (codec->header) {
		niobuffer_free(codec->header);
		codec->header = NULL;
	}

	if (codec->wrapper) {
		niobuffer_free(codec->wrapper);
		codec->wrapper = NULL;
	}

	rc = 0;

finally:
	return rc;
}

int niohttpd_server_codec_handle_upstream(void *obj,
		struct niochannel *channel, void *message)
{
	int rc;
	struct niohttpd_server_codec *codec = obj;
	struct niobuffer *buffer;
	struct niohttp_request *req;

	if (!codec || !channel || !message) {
		rc = NIOHTTPD_EINVAL;
		niohttpd_error("null argument(s)");
		goto finally;
	}

	niohttpd_debug("decoding: %s", codec->name);

	buffer = message;

	if ((rc = niohttp_request_decoder_parse(codec->decoder, buffer,
			&req))) {
		niohttpd_error("failed to parse: %s", strerror(rc));
		goto finally;
	}

	if (req) {
		if ((rc = niochannel_send_upstream(channel,
				codec->name, req))) {
			niohttpd_error("failed to send upstream: %s",
					nio_strerror(rc));
			rc = NIOHTTPD_EIO;
			goto finally;
		}
	}

finally:
	return rc;
}

int niohttpd_server_codec_handle_downstream(void *obj,
		struct niochannel *channel, void *message)
{
	int rc;
	struct niohttpd_server_codec *codec = obj;
	struct niohttp_response *resp;
	struct niobuffer *content;

	if (!codec || !channel || !message) {
		rc = NIOHTTPD_EINVAL;
		niohttpd_error("null argument(s)");
		goto finally;
	}

	niohttpd_debug("encoding: %s", codec->name);

	resp = message;
	content = niochannel_buffer(channel);

	if (!(codec->header = niobuffer_slice(content,
			niobuffer_limit(content),
			niobuffer_capacity(content) -
			niobuffer_limit(content)))) {
		niohttpd_error("failed to slice header buffer");
		rc = -1;
		goto finally;
	}

	if ((rc = niohttp_response_encoder_format(codec->encoder,
			resp, codec->header))) {
		niohttpd_error("failed to format response: %s",
				niohttpd_strerror(rc));
		goto finally;
	}

	niobuffer_flip(codec->header);

	if (!(codec->wrapper = niobuffer_wrapper())) {
		niohttpd_error("failed to create wrapper");
		rc = NIOHTTPD_ENOMEM;
		goto finally;
	}

	if ((rc = niobuffer_wrap(codec->wrapper, codec->header))) {
		niohttpd_error("failed to wrap header: %s", nio_strerror(rc));
		goto finally;
	}

	if ((rc = niobuffer_wrap(codec->wrapper, content))) {
		niohttpd_error("failed to wrap content: %s", nio_strerror(rc));
		goto finally;
	}

	if ((rc = niochannel_send_downstream(channel,
			codec->name, codec->wrapper))) {
		niohttpd_error("failed to send downstream: %s",
				nio_strerror(rc));
		rc = NIOHTTPD_EIO;
		goto finally;
	}

	rc = 0;

finally:
	return rc;
}
